<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8">
		<title>我的写字机器人</title>
		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
		<style type="text/css">
			body {
				font: 16px/1.7 jaf-bernino-sans, 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Geneva, Verdana, sans-serif;
				
			}
			
			::selection {
				background-color: honeydew;
				color: #ea3e0a;
			}
			
			@media only screen and (min-width: 800px) {
				article {
					width: 800px;
				}
			}
			
			@media only screen and (max-width: 800px) {
				article {
					width: auto;
				}
			}
			
			article {
				margin: 0 auto;
			}
			/****标题样式****/
			article .article-info {
				overflow: hidden;
			}
			
			article .article-info img {
				float: left;
				border-radius: 50%;
				border: 3px solid white;
				box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
				margin-right: 15px;
			}
			
			article .article-info .author {
				color: #999999;
				font-size: 12px;
				padding: 0 2px;
			}
			/****标题样式****/
			/****引用其他文章样式****/
			article blockquote {
				background: #f9f9f9;
				border-left: 10px solid #ccc;
				margin: 1em 10px;
				padding: 0.5em 10px;
				position: relative;
				quotes: "\201C" "\201D" "\2018" "\2019";
			}
			
			article blockquote:before {
				color: #ccc;
				content: "";
				font-size: 2em;
				position: absolute;
			}
			
			article blockquote p {
				text-indent: 0.5em;
				margin: 0 !important;
			}
			/****引用其他文章样式****/
			/****超链接样式****/
			article a:link {
				color: #ea3e0a;
				margin: 0 2px;
				overflow-wrap: break-word;
			}
			
			article .content h2 {
				margin: 60px 0 10px;
				padding: 0 0 0 20px;
				font-size: 18px;
				border-left: 3px solid orange;
			}
			
			article .content h3 {
				margin: 40px 5px 10px;
				font-size: 14px;
				border-bottom: 2px solid #E0DDD7;
			}
			/****超链接样式****/
			
			
		</style>
	</head>

	<body>
		<article>
			<div class="article-info">
				<img  width="60px" src="../img/myheader.jpg" />
				<h3 style="margin: 15px 0 5px 0;">关于css的position的各类区别relative/absolute/fixed/static</h3>
				<div class="author">spring | 2015-10-15</div>
			</div>
			<div class="content">
				<blockquote>
					<p>原文地址: <a href="http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html" target="_blank">http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html</a> 已取得原作者同意</p>
				</blockquote>
				<p>用Javascript的小伙伴们，是时候承认了，关于 promises 我们一直存在着问题。并非说 promises 本身有问题，Promises/A+ 是极好的。</p>

				<p>就我过去数年观察大量 PouchDB API 以及其他 promise-heavy API 的使用者们与这些 API 的搏斗中我发现，最大的问题是：</p>

				<p><em>大部分使用 promises 的小伙伴们并没有真正的理解它</em></p>

				<p>如果你不认同这个观点，可以看看我最近在 <a href="https://twitter.com/nolanlawson/status/578948854411878400" target="_blank">twitter</a> 上出的这道题：</p>

				<p>Q: 下面的四种 promises 的区别是什么</p>
				
				<p>如果你知道正常答案，那么我要恭喜你，你是一位 promises 大拿，你完全可以不再继续阅读这篇文件。</p>

				<p>另外 99.99% 的小伙伴们，你们才是正义。没有一个人在 twitter 上给出正确答案，甚至我自己也被 #3 的答案惊到了。恩，即使这道题是我自己出的。</p>

				<p>正确答案在这篇文章的结尾，在此之前，我希望首先探究一下为何为何 promises 如此复杂，并且为何有这么多人，无论是新手还是专家，都被它坑到了。同时我还会给出一个我自认为非常独特的视角，可以让 promises 变的更加容易理解。同时，我非常确信在了解这些之后，promises 并不会再难以理解。</p>

				<p>不过在开始这些之前，让我们先了解一些 promises 的基础知识。</p>

				<h2>Promises 的起源</h2>

				<p>如果你阅读了 promises 的一些相关文献，你会发现有一个词 <a href="https://medium.com/@wavded/managing-node-js-callback-hell-1fe03ba8baf" target="_blank">金字塔问题</a> 经常出现。它描述的是大量的回调函数慢慢向右侧屏幕延伸的一种状态。</p>

				<p>Promises 的确解决了这个问题，并且不仅仅是缩进问题。就像在 <a href="http://youtu.be/hf1T_AONQJU" target="_blank">Callback Hell的救赎</a> 中描述的一样，回调函数真正的问题在于他剥夺了我们使用 <code>return</code> 和 <code>throw</code> 这些关键字的能力。相反，我们的整个代码流程都是基于副作用的: 一个函数会附带调用其他函数。</p>

				<blockquote>
					<p>原文关于副作用的描述并不能很直观的进行理解，建议参考 <a href="https://en.wikipedia.org/wiki/Side_effect_(computer_science)" target="_blank">WIKI</a> 。简单来说就是一个函数除了会返回一个值之外，还会修改函数以外的状态如全局变量等等。实际上所有异步调用都可以视为带有副作用的行为。译者注。</p>
				</blockquote>

				<p>并且实际上，回调更加恼人的是，他会将我们通常在大部分编程语言中能获得的 <em>堆栈</em> 破坏。编写代码时如果无法获取堆栈就像开车时没有刹车一样: 不到用的时候，你不会知道它有多重要。</p>

				<p>Promises 给予我们的就是在我们使用异步时丢失的最重要的语言基石: <code>return</code>, <code>throw</code> 以及堆栈。但是想要 promises 能够提供这些便利给你的前提是你知道如何正确的使用它们。</p>

				<h2>新手错误</h2>

				<p>一些同学试图通过用 <a href="http://andyshora.com/promises-angularjs-explained-as-cartoon.html" target="_blank">卡通</a> 来描述 promises，或者试图用语言去描述它: "哦，你可以把它作为一个异步的值进行传递。"</p>

				<p>我认为这些解释并不会有很大的帮助。对我来说，promises 完全是一种代码结构和流程。因此我认为直接展示一些常见的错误并且演示如何修复它们更能够说明问题。我说这些问题是 "新手问题" ，这意味着 "虽然你现在是一个新手，孩子，但是马上你会变成一位专家"。</p>

				<p>小插曲: "promises" 对于不同的人有不同的理解和观点，但是在这篇文章中我特指 <a href="https://promisesaplus.com/" target="_blank">正式标准</a> ，在现代浏览器中暴露为 <code>window.Promise</code>。虽然并非所有浏览器都有 <code>windows.Promise</code>，但是可以寻找一些 pollyfill ，比如 <a href="https://github.com/calvinmetcalf/lie"
					target="_blank">Lie</a> 是目前体积最小的兼容标准的库。</p>

				<h3>新手错误 #1： promise版的金字塔问题</h3>

				<p>观察大家如何使用 PouchDB 这类大型的 promise 风格的API，我发现大量错误的 promise 使用形式。最常见的错误就是下面这个：</p>
				
				<p>是的，实际上你可以像使用回调一样使用 promises，恩，就像用打磨机去削脚趾甲一样，你确实可以这么做。</p>

				<p>并且如果你以为这样的错误只限于初学者，那么你会惊讶于我实际上是在黑莓官方开发者博客上看到上面的代码。老的回调风格的习惯难以消灭。(至开发者: 抱歉选了你的例子，但是你的例子将会有积极的教育意义)</p>

				<p>正确的风格应该是这样:</p>
				
				<p>这种写法被称为 <code>composing promises</code> ，是 promises 的强大能力之一。每一个函数只会在前一个 promise 被调用并且完成回调后调用，并且这个函数会被前一个 promise 的输出调用，稍后我们在这块做更多的讨论。</p>

				<h3>新手错误 #2： WTF, 用了 promises 后怎么用 forEach?</h3>

				<p>这里是大多数人对于 promises 的理解开始出现偏差。一旦当他们要使用他们熟悉的 forEach() 循环 (无论是 for 循环还是 while 循环)，他们完全不知道如何将 promises 与其一起使。因此他们就会写下类似这样的代码。</p>
				
				<p>这份代码有什么问题？问题在于第一个函数实际上返回的是 <code>undefined</code>，这意味着第二个方法不会等待所有 documents 都执行 db.remove()。实际上他不会等待任何事情，并且可能会在任意数量的文档被删除后执行！</p>

				<p>这是一个非常隐蔽的 bug，因为如果 PouchDB 删除这些文档足够快，你的 UI 界面上显示的会完成正常，你可能会完全注意不到有什么东西有错误。这个 bug 可能会在一些古怪的竞态问题或一些特定的浏览器中暴露出来，并且到时可能几乎没有可能去定位问题。</p>

				<p>简而言之，forEach()/for/while 并非你寻找的解决方案。你需要的是 Promise.all():</p>
				
				<p>上面的代码是什么意思呢？大体来说，Promise.all()会以一个 promises 数组为输入，并且返回一个新的 promise。这个新的 promise 会在数组中所有的 promises 都成功返回后才返回。他是异步版的 for 循环。 </p>

				<p>并且 Promise.all() 会将执行结果组成的数组返回到下一个函数，比如当你希望从 PouchDB 中获取多个对象时，会非常有用。此外一个更加有用的特效是，一旦数组中的 promise 任意一个返回错误，Promise.all() 也会返回错误。</p>

				<h3>新手错误 #3： 忘记使用 .catch()</h3>

				<p>这是另一个常见的错误。单纯的坚信自己的 promises 会永远不出现异常，很多开发者会忘记在他们的代码中添加一个 .catch()。然而不幸的是这也意味着，任何被抛出的异常都会被吃掉，并且你无法在 console 中观察到他们。这类问题 debug 起来会非常痛苦。</p>

				<blockquote>
					<p>类似 Bluebird 之类的 Promise 库会在这种场景抛出 UnhandledRejectionError 警示有未处理的异常，这类情况一旦发现，就会造成脚本异常，在 Node 中更会造成进程 Crash 的问题，因此正确的添加 .catch() 非常重要。 译者注</p>
				</blockquote>

				<p>为了避免这类讨厌的场景，我习惯于像下面的代码一样使用 promise:</p>
				
				<p>即使你坚信不会出现异常，添加一个 catch() 总归是更加谨慎的。如果你的假设最终被发现是错误的，它会让你的生活更加美好。</p>

				<h3>新手错误 #4：使用 "deferred"</h3>

				<p>这是一个我<a href="http://gonehybrid.com/how-to-use-pouchdb-sqlite-for-local-storage-in-your-ionic-app/" target="_blank">经常可以看到的错误</a>，以至于我甚至不愿意在这里重复它，就像惧怕 Beetlejuice 一样，仅仅是提到它的名字，就会召唤出来更多。</p>

				<p>简单的说，promises 拥有一个漫长并且戏剧化的历史，Javascript 社区花费了大量的时间让其走上正轨。在早期，deferred 在 Q，When，RSVP，Bluebird，Lie等等的 “优秀” 类库中被引入， jQuery 与 Angular 在使用 ES6 Promise 规范之前，都是使用这种模式编写代码。</p>

				<p>因此如果你在你的代码中使用了这个词 (我不会把这个词重复第三遍！)，你就做错了。下面是说明一下如何避免它。</p>

				<p>首先，大部分 promises 类库都会提供一个方式去包装一个第三方的 promises 对象。举例来说，Angular的 <code>$q</code> 模块允许你使用 <code>$q.when</code> 包裹非 <code>$q</code> 的 promises。因此 Angular 用户可以这样使用 PouchDB promises.</p>
				
				<p>另一种策略是使用<a href="https://blog.domenic.me/the-revealing-constructor-pattern/" target="_blank">构造函数声明模式</a>，它在用来包裹非 promise API 时非常有用。举例来说，为了包裹一个回调风格的 API 如 Node 的 <code>fs.readFile</code> ，你可以简单的这么做:</p>
				
				<p>完工！我们打败了可怕的 def....啊哈，抓到自己了。:)</p>

				<blockquote>
					<p>关于为何这是一种反模式更多的内容，请查看 Bluebird 的 <a href="https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern" target="_blank">promise anti-patterns</a> wiki 页</p>
				</blockquote>

				<h3>新手错误 #5：使用副作用调用而非返回</h3>

				<p>下面的代码有什么问题？</p>
				
				<p>好了，现在是时候讨论一下关于 promises 你所需要知道的一切。</p>

				<p>认真的说，这是一个一旦你理解了它，就会避免所有我提及的错误的古怪的技巧。你准备好了么？</p>

				<p>就如我前面所说，promises 的奇妙在于给予我们以前的 <code>return</code> 与 <code>throw</code>。但是在实践中这到底是怎么一回事呢？</p>

				<p>每一个 promise 都会提供给你一个 <code>then()</code> 函数 (或是 <code>catch()</code>，实际上只是 <code>then(null, ...)</code> 的语法糖)。当我们在 <code>then()</code> 函数内部时：</p>
				
				<p>我们可以做什么呢？有三种事情：</p>

				<ol>
					<li><code>return</code> 另一个 promise</li>
					<li><code>return</code> 一个同步的值 (或者 <code>undefined</code>)</li>
					<li><code>throw</code> 一个同步异常</li>
				</ol>

				<p>就是这样。一旦你理解了这个技巧，你就理解了 promises。因此让我们逐个了解下。</p>

				<ol>
					<li>
						<p>返回另一个 promise</p>

						<p>这是一个在 promise 文档中常见的使用模式，也就是我们在上文中提到的 "composing promises"：</p>
						
						<p>注意到我是 <code>return</code> 第二个 promise，这个 <code>return</code> 非常重要。如果我没有写 <code>return</code>，<code>getUserAccountById()</code> 就会成为一个副作用，并且下一个函数将会接收到 <code>undefined</code> 而非 <code>userAccount</code>。</p>
					</li>
					<li>
						<p>返回一个同步值 (或者 undefined)</p>

						<p>返回 <code>undefined</code> 通常是错误的，但是返回一个同步值实际上是将同步代码包裹为 promise 风格代码的一种非常赞的手段。举例来说，我们对 users 信息有一个内存缓存。我们可以这样做：</p>
						
						<p>是不是很赞？第二个函数不需要关心 <code>userAccount</code> 是从同步方法还是异步方法中获取的，并且第一个函数可以非常自由的返回一个同步或者异步值。</p>

						<p>不幸的是，有一个不便的现实是在 JavaScript 中无返回值函数在技术上是返回 <code>undefined</code>，这就意味着当你本意是返回某些值时，你很容易会不经意间引入副作用。</p>

						<p>出于这个原因，我个人养成了在 <code>then()</code> 函数内部 <em>永远返回或抛出</em> 的习惯。我建议你也这样做。</p>
					</li>
					<li>
						<p>抛出同步异常</p>

						<p>谈到 <code>throw</code>，这是让 promises 更加赞的一点。比如我们希望在用户已经登出时，抛出一个同步异常。这会非常简单：</p>
						
						<p>如果用户已经登出，我们的 <code>catch()</code> 会接收到一个同步异常，并且如果 <em>后续的 promise 中出现异步异常</em>，他也会接收到。再强调一次，这个函数并不需要关心这个异常是同步还是异步返回的。</p>

						<p>这种特性非常有用，因此它能够在开发过程中帮助定位代码问题。举例来说，如果在 <code>then()</code> 函数内部中的任何地方，我们执行 <code>JSON.parse()</code>，如果 JSON 格式是错误的，那么它就会抛出一个异常。如果是使用回调风格，这个错误很可能就会被吃掉，但是使用 promises，我们可以轻易的在 <code>catch()</code> 函数中处理它了。</p>
					</li>
				</ol>

				<h2>进阶错误</h2>


			</div>
			</div>
		</article>

	</body>

</html>